﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using UnitBox.Fixtures;
using World.Testing;

namespace UnitBox {
	[TestClass]
	public class CreateSpecs : SpecContext {

		#region context setup/teardown

		[TestCleanup]
		public virtual void TestCleanup() {
			// ensure no interceptions affect tests. this keeps tests isolated
			UnitBoxInterceptions.ResetAllInterceptions();
		}

		#endregion


		#region contexts

		[TestClass]
		public class WhenNoSwapHasYetBeenConfigured : CreateSpecs {

			#region specs

			[TestMethod]
			public virtual void WhenRequestingANewInstance_ShouldCallTheRealCreationFunction() {
				// request creation of a new instance
				Thing realInstance = new Thing();
				Thing createdInstance = Create.New( () => realInstance );

				// ensure the real creation function got created
				EnsureRealInstanceWasCreated( createdInstance, realInstance );
			}

			#endregion

		}


		[TestClass]
		public class WhenConfiguredToReturnASwappedInstance : CreateSpecs {

			#region context objects

			internal Thing FakeInstance { get; set; }

			#endregion


			#region context setup

			protected override void ContextSetup() {
				base.ContextSetup();

				// create a fake
				FakeInstance = new Thing();

				// configure to return the fake instance upon the next creation
				Swap.NextInstanceOf<Thing>().With( FakeInstance );
			}

			#endregion


			#region specs

			[TestMethod]
			public virtual void WhenRequestingANewInstance_ShouldReturnTheSwappedInstance() {
				// request creation of a new instance
				Thing realInstance = new Thing();
				Thing createdInstance = Create.New( () => realInstance );

				// ensure the fake instance was returned
				EnsureSwappedInstanceWasCreated( createdInstance, realInstance, FakeInstance );
			}


			[TestMethod]
			public virtual void WhenConfiguringASubsequentInstanceOfTheSameType_ShouldReturnTheSwappedInstancesInTheSameOrderTheyWereConfigured() {
				Thing firstFakeInstance = FakeInstance;
				Thing firstRealInstance = new Thing();
				Thing secondRealInstance = new Thing();

				// configure another instance of the same type to be swapped
				var secondFakeInstance = new Thing();
				Swap.NextInstanceOf<Thing>().With( secondFakeInstance );

				// request creation of two new instances
				Thing firstCreatedInstance = Create.New( () => firstRealInstance );
				Thing secondCreatedInstance = Create.New( () => secondRealInstance );

				// ensure they got returned in the given order
				EnsureSwappedInstanceWasCreated( firstCreatedInstance, firstRealInstance, firstFakeInstance );
				EnsureSwappedInstanceWasCreated( secondCreatedInstance, secondRealInstance, secondFakeInstance );
			}


			[TestMethod]
			public virtual void AfterReturningTheSwappedInstance_WhenRequestingSubsequentInstances_ShouldCallTheRealCreationFunctions() {
				// request a new instance so that the swapped one gets returned
				WhenRequestingANewInstance_ShouldReturnTheSwappedInstance();

				Thing secondRealInstance = new Thing();
				Thing thirdRealInstance = new Thing();

				// request subsequent creations
				Thing secondCreatedInstance = Create.New( () => secondRealInstance );
				Thing thirdCreatedInstance = Create.New( () => thirdRealInstance );

				// ensure subsequent creations called the real creation functions
				EnsureRealInstanceWasCreated( secondCreatedInstance, secondRealInstance );
				EnsureRealInstanceWasCreated( thirdCreatedInstance, thirdRealInstance );
			}

			#endregion


			
#if DEBUG

			#region sub-contexts

			[TestClass]
			public class OnceLockedDown : WhenConfiguredToReturnASwappedInstance {

				#region context setup

				protected override void ContextSetup() {
					base.ContextSetup();

					UnitBoxInterceptions.Lockdown();
				}

				#endregion


				#region specs

				[TestMethod]
				public void WhenRequestingANewInstance_ShouldCallTheRealCreationFunction() {
					// request creation of a new instance
					Thing realInstance = new Thing();
					Thing createdInstance = Create.New( () => realInstance );

					// ensure the real creation function got created
					EnsureRealInstanceWasCreated( createdInstance, realInstance );
				}

				#endregion


				#region non-specs

				[SpecNotApplicableInThisContext]
				public override void WhenRequestingANewInstance_ShouldReturnTheSwappedInstance() {
				}


				[SpecNotApplicableInThisContext]
				public override void WhenConfiguringASubsequentInstanceOfTheSameType_ShouldReturnTheSwappedInstancesInTheSameOrderTheyWereConfigured() {
				}

				#endregion

			}

			#endregion

#endif

		}

		#endregion


		#region helpers

		protected static void EnsureRealInstanceWasCreated( object createdInstance, object realInstance ) {
			createdInstance.ShouldBeSameInstanceAs( realInstance,
				"The creation function did not return the real instance of '{0}' even though it was not configured to do otherwise.", createdInstance.GetType() );
		}


		protected static void EnsureSwappedInstanceWasCreated( object createdInstance, object realInstance, object swappedInstance ) {
			createdInstance.ShouldNotBeSameInstanceAs( realInstance,
				"The creation function returned the real instance of '{0}' even though it was configured to return another one instead.", createdInstance.GetType() );
			createdInstance.ShouldBeSameInstanceAs( swappedInstance,
				"The creation function did not return the swapped instance of '{0}' even though it was configured to do so.", createdInstance.GetType() );
		}


		#endregion

	}
}
